# vim:ts=4:sw=4:noet:
""" Lexer """

class Token:
	EOF, ID, BIN, INT, STR, SYM, FLT = range(7)
	typenames = ["EOF", "ID ", "BIN", "INT", "STR", "SYM", "FLT"]
	prettyTypenames = [ "EOF", "Identifier", "Binary message", "Integer", "String", "Symbol", "Float" ]

	def __init__(self, index, type, value=None, numchars=None):
		self.index = index
		self.type = type
		self.value = value
		if not numchars:
			if self.value:
				self.numchars = len(self.value)
			else:
				self.numchars = 0
		else:
			self.numchars = numchars

	def isName(self):
		if ((self.type in [Token.STR, Token.SYM, Token.EOF]) or
				(not self.value[0].isalpha())):
			return False
		return self.value[-1].isalpha() or self.value[-1].isdigit()

	def isKeyword(self):
		if (self.type == Token.EOF or (not self.value[0].isalpha())):
			return False
		return self.value[-1]==":"

	def isBinary(self):
		if self.type == Token.EOF:
			return False
		return not (self.isName() or 
					self.isKeyword() or 
					self.value[0] in ".()[]#^$;'")

class Lexer:

	def __init__(self, status, text):
		self.status = status
		self.text = text
		self.index = 0
		self.lineNum = 1
		self.token = None
		self.oldtoken = None
		self.lastComment = ""
		self.linestack = []
		self.idxofs = 0

	def pushLine(self):
		idx = self.index
		if self.token:
			idx = self.token.index
		self.linestack.append((self.lineNum, idx))

	def popLine(self):
		if len(self.linestack)==0:
			return (self.lineNum, self.lineNum, self.index, self.index)
		ln = self.linestack.pop()
		idx = self.index
		if self.oldtoken:
			idx = self.oldtoken.index+len(self.oldtoken.value)
		ln = (ln[0], self.lineNum, ln[1]-self.idxofs, idx-self.idxofs)
		return ln
	
	def currentChar(self, n=0):
		if (self.index+n)<len(self.text):
			return self.text[self.index+n]
		return None

	def nextChar(self):
		if self.currentChar() == "\n":
			self.lineNum += 1
		self.index += 1
		return self.currentChar()

	def getText(self, frm, to):
		return self.text[frm.index:to.index+1+to.numchars]

	def skipBlanks(self):
		c = self.currentChar()
		while c and c in " \t\r\n":
			c = self.nextChar()
		if c=="\"":
			self.skipComment()
	
	def skipComment(self):
		cc = self.nextChar()
		comment = cc
		while cc!="\"":
			cur = self.currentChar()
			cc = self.nextChar()
			if (cur=="\\" and cc=="\""):
				comment = comment[:-1]+"\""
				cc = 'a'
			else:
				comment += cc
			if cc==None:
				self.status.error("Unterminated comment")
		self.lastComment = comment[:-1]
		self.nextChar()
		self.skipBlanks()

	def saveState(self):
		self.back_oldtoken = self.oldtoken
		self.back_token = self.token
		self.back_index = self.index
		self.back_lineNum = self.lineNum
		self.back_lastComment = self.lastComment

	def backtrack(self):
		self.oldtoken = self.back_oldtoken
		self.token = self.back_token
		self.index = self.back_index
		self.lineNum = self.back_lineNum
		self.lastComment = self.back_lastComment

	def nextLex(self):
		self.oldtoken = self.token
		self.skipBlanks()
		c = self.currentChar()
		if c==None:
			self.token = Token(self.index, Token.EOF)
		elif c.isdigit():
			self.lexNumber()
		elif c.isalpha():
			self.lexAlnum()
		elif c == "'":
			self.lexString()
		elif c == "#":
			self.lexSymbol()
		else:
			self.lexBinary()

	def lexNumber(self):
		start = self.index
		while self.nextChar().isdigit() :
			pass
		if self.currentChar()=="r":
			self.nextChar()
			cc = self.nextChar()
			while cc.isdigit() or cc in "ABCDEF":
				cc = self.nextChar()
		elif self.currentChar()=="." and self.currentChar(1).isdigit():
			self.nextChar()
			cc = self.nextChar()
			while cc.isdigit():
				cc = self.nextChar()
			self.token = Token(start, Token.FLT, self.text[start:self.index])
			return
		self.token = Token(start, Token.INT, self.text[start:self.index])

	def lexAlnum(self):
		start = self.index
		cc = self.nextChar()
		while cc.isdigit() or cc.isalpha() or cc==":":
			cc = self.nextChar()
		self.token = Token(start, Token.ID,self.text[start:self.index])

	def charIsSyntax(self, c):
		return (c in ".()[]#^$;'")

	def lexBinary(self):
		c = self.currentChar()
		self.token = Token(self.index-0, Token.BIN, c)
		d = self.nextChar()
		if self.charIsSyntax(c):
			return self.token
		if (d in " \t\n\r") or d.isdigit() or d.isalpha() or self.charIsSyntax(d):
			return self.token
		c += d
		self.token = Token(self.index-1, Token.BIN, c)
		self.nextChar()

	def lexString(self):
		self.index += 1
		first = self.index
		cc = self.currentChar()
		while cc!="'":
			if cc==None:
				self.status.error("Unterminated string")
			self.index += 1
			cc = self.currentChar()
		self.index += 1
		last = self.index - 1
		self.token = Token(first-1, Token.STR, self.text[first:last], len(self.text[first:last])+2)
		return self.text[first:last]

	def lexSymbol(self):
		self.nextChar()
		cc = self.currentChar()
		if cc==None or cc in " \t\r\n":
			self.status.error("Invalid symbol")
		if cc=="(":
			self.token = Token(self.token.index, Token.BIN, "#(")
			self.nextChar()
			return self.token
		if self.charIsSyntax(cc):
			self.status.error("Invalid symbol")
		self.nextLex()
		self.token = Token(self.token.index, Token.SYM, self.token.value)
		cc =  self.token
		return cc.value

